/*
 *
 *
 */

#include "gkt_board.h"
#include "gkt_os.h"
#include "gkt_timer.h"
#include "gkt_debug.h"
#include "gkt_io_debounce.h"

#ifdef GKT_CONFIG_IO_DEBOUNCE_TABLE

#ifdef GKT_CONFIG_IO_DEBOUNCE_HW_TIMER_DEVICE_ID
#define IO_DEBOUNCE_HW_TIMER_DEVICE_ID	\
	GKT_CONFIG_IO_DEBOUNCE_HW_TIMER_DEVICE_ID
#else
#define IO_DEBOUNCE_HW_TIMER_DEVICE_ID	0
#endif

#ifdef GKT_CONFIG_IO_DEBOUNCE_HW_TIMER_CHANNEL_ID
#define IO_DEBOUNCE_HW_TIMER_CHANNEL_ID	\
	GKT_CONFIG_IO_DEBOUNCE_HW_TIMER_CHANNEL_ID
#else
#define IO_DEBOUNCE_HW_TIMER_CHANNEL_ID	0
#endif

#define IO_DEBOUNCE_STEP_MS	10
#define IO_DEBOUNCE_COUNTER(duration_ms)	\
	(((duration_ms) + IO_DEBOUNCE_STEP_MS - 1) / IO_DEBOUNCE_STEP_MS)
#define IO_DEBOUNCE_MIN_DURATION_MS	50
#define IO_DEBOUNCE_OK_COUNTER	2

typedef struct _io_debounce_item {
	gkt_io_debounce_attr_s	attr;
	int	detect_state;
	int	max_counter;
	int	ok_counter;
} io_debounce_item_s;

typedef struct _io_debounce {
	int	init_ok;

	gkt_semaphore_t	semaphore;
	gkt_handle_t	hw_timer_id;

	uint32_t	item_active_bits;
	uint32_t	item_end_bits;
	uint32_t	result_ok_bits;
	uint32_t	detect_state_bits;

	io_debounce_item_s	items[GKT_IO_DEBOUNCE_ID_MAX];
} io_debounce_s;
static io_debounce_s s_io_debounce;

static void io_debounce_hw_timer_callback(gkt_handle_t timer_handle)
{
	io_debounce_item_s *item;
	uint32_t id, mask;
	int detect_state, result;

	for (id = 0; id < GKT_IO_DEBOUNCE_ID_MAX; id++) {
		mask = 1U << id;
		item = &s_io_debounce.items[id];
		if (s_io_debounce.item_active_bits & mask) {
			result = GKT_IO_DEBOUNCE_MAX;
			if (item->max_counter > 0) {
				item->max_counter--;
				if (!item->max_counter)
					result = GKT_IO_DEBOUNCE_FAIL;
			}
			
			detect_state = (*item->attr.detect_fn)();
			if (detect_state == item->detect_state) {
				item->ok_counter++;
				if (IO_DEBOUNCE_OK_COUNTER == item->ok_counter)
					result = GKT_IO_DEBOUNCE_OK;
			}
			else
				item->ok_counter = 0;

			if (result != GKT_IO_DEBOUNCE_MAX) {
				s_io_debounce.item_active_bits &= ~mask;

				if (GKT_IO_DEBOUNCE_OK == result)
					s_io_debounce.result_ok_bits |= mask;
				else
					s_io_debounce.result_ok_bits &= ~mask;
				if (item->detect_state)
					s_io_debounce.detect_state_bits |= mask;
				else
					s_io_debounce.detect_state_bits &= ~mask;
				s_io_debounce.item_end_bits |= mask;
			}
		}
	}

	if (!s_io_debounce.item_active_bits)
		gkt_timer_stop(s_io_debounce.hw_timer_id);

	if (s_io_debounce.item_end_bits)
		gkt_semaphore_release(s_io_debounce.semaphore);
}

static void io_debounce_notify_task(void *params)
{
	io_debounce_item_s *item;
	uint32_t item_end_bits, result_ok_bits, detect_state_bits;
	uint32_t id, mask;
	int result, detect_state;

	do {
		gkt_semaphore_acquire(s_io_debounce.semaphore, GKT_INFINITE);

		gkt_irq_global_disable();
		item_end_bits = s_io_debounce.item_end_bits;
		result_ok_bits = s_io_debounce.result_ok_bits;
		detect_state_bits = s_io_debounce.detect_state_bits;
		s_io_debounce.item_end_bits = 0;
		s_io_debounce.result_ok_bits = 0;
		s_io_debounce.detect_state_bits = 0;
		gkt_irq_global_enable();

		gkt_info("iodb_task: 0x%x - (0x%x - 0x%x)\n", 
			item_end_bits, result_ok_bits, detect_state_bits);

		for (id = 0; id < GKT_IO_DEBOUNCE_ID_MAX; id++) {
			mask = 1U << id;
			if (item_end_bits & mask) {
				item = &s_io_debounce.items[id];
				
				result = (result_ok_bits & mask) ? GKT_IO_DEBOUNCE_OK : GKT_IO_DEBOUNCE_FAIL;
				detect_state = (detect_state_bits & mask) ? 1 : 0;
				(*item->attr.complete_fn)(result, detect_state);
			}
		}
	} while (1);
}

int gkt_io_debounce_config(uint32_t id, 
				gkt_io_debounce_attr_s *attr)
{
	io_debounce_item_s *item;

	if ((id < GKT_IO_DEBOUNCE_ID_MAX)
		&& attr && attr->detect_fn
		&& attr->complete_fn)
	{
		if (!s_io_debounce.init_ok) {
			gkt_error("io_debounce_config: not init!\n");
			return GKT_ENOTINIT;
		}

		item = &s_io_debounce.items[id];
		item->attr.detect_fn = attr->detect_fn;
		item->attr.complete_fn = attr->complete_fn;

		return GKT_SUCCESS;
	}

	gkt_error("io_debounce_config: type(%u) args invalid!\n", id);
	return GKT_EPARAM;
}

int gkt_io_debounce_start(uint32_t id, 
				int detect_state, uint32_t duration_ms)
{
	io_debounce_item_s *item;
	int max_counter, need_start_hw_timer, retval;

	if (s_io_debounce.init_ok 
		&& (id < GKT_IO_DEBOUNCE_ID_MAX)) 
	{
		item = &s_io_debounce.items[id];
		if (!item->attr.detect_fn || !item->attr.complete_fn)
			return GKT_ENOTCONFIG;

		gkt_info("iodb_start: (%u) state(%d)\n", id, detect_state);

		if (duration_ms <= IO_DEBOUNCE_MIN_DURATION_MS)
			max_counter = IO_DEBOUNCE_COUNTER(IO_DEBOUNCE_MIN_DURATION_MS);
		else if (duration_ms < GKT_INFINITE)
			max_counter = IO_DEBOUNCE_COUNTER(duration_ms);
		else
			max_counter = -1;

		gkt_irq_global_disable();
		item->detect_state = detect_state ? 1 : 0;
		item->max_counter = max_counter;
		item->ok_counter = 0;
		need_start_hw_timer = s_io_debounce.item_active_bits ? 0 : 1; 
		s_io_debounce.item_active_bits |= 1U << id;
		gkt_irq_global_enable();

		if (need_start_hw_timer) {
			retval = gkt_timer_start(s_io_debounce.hw_timer_id);
			if (retval != GKT_SUCCESS) {
				gkt_error("io_debounce_start: start timer failed.\n");
				return retval;
			}
		}

		return GKT_SUCCESS;
	}

	return GKT_EPERMIT;
}

int gkt_io_debounce_init(void)
{
	gkt_thread_t thread;
	gkt_base_timer_cfg_attr_s cfg_attr;
	int retval;

	if (s_io_debounce.init_ok)
		return GKT_SUCCESS;

#ifdef IO_DEBOUNCE_TEST_GPIO_PIN
	gkt_gpio_config(IO_DEBOUNCE_TEST_GPIO_PIN,
		GKT_GPIO_F_OUTPUT, NULL);
#endif

	s_io_debounce.semaphore = gkt_semaphore_create(1, 0);
	if (!s_io_debounce.semaphore) {
		gkt_error("io_debounce_init: create semaphore failed!\n");
		return GKT_ECREATE;
	}

	thread = gkt_thread_create(io_debounce_notify_task,
					NULL, GKT_THREAD_PRIORITY_HIGH,
					0x400, NULL);
	if (!thread) {
		gkt_error("io_debounce_init: create task failed!\n");
		return GKT_ECREATE;
	}

	s_io_debounce.hw_timer_id = 
		gkt_timer_open(GKT_TIMER_TYPE_BASE, 
			IO_DEBOUNCE_HW_TIMER_DEVICE_ID,
			IO_DEBOUNCE_HW_TIMER_CHANNEL_ID);
	if (GKT_HANDLE_INVALID == s_io_debounce.hw_timer_id) {
		gkt_error("io_debounce_init: open hw_timer failed!\n");
		return GKT_ELOWLEVEL;
	}

	cfg_attr.mode = GKT_TIMER_MODE_TIMER;
	cfg_attr.timer.interval = IO_DEBOUNCE_STEP_MS * 1000;
	cfg_attr.timer.auto_reload = 1;
	cfg_attr.timer.callback = io_debounce_hw_timer_callback;
	retval = gkt_timer_config(s_io_debounce.hw_timer_id, &cfg_attr);
	if (retval != GKT_SUCCESS) {
		gkt_error("io_debounce_init: config hw_timer failed!\n");
		return retval;
	}

	s_io_debounce.init_ok = 1;

	return GKT_SUCCESS;
}

#endif

